﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows;
using System.Collections;

namespace SmartPaginator.Elements
{
    public abstract class BlockContainerElement: ItemsControl, IBlockElement, IBlockContainer
    {
        #region Constructor

        protected BlockContainerElement(TextElement source)
        {
            Source = source;

            var block = source as Block;
            if (block != null)
                this.BreakPageBefore = block.BreakPageBefore;
        }

        public BlockContainerElement(TextElement source, IList collection, IElementFactory factory): this(source)
        {
            if (collection != null)
            {
                if (factory == null)
                    throw new ArgumentNullException("factory");

                foreach (object block in collection)
                {
                    var element = factory.GetElementForContent(block);
                    this.Items.Add(element);
                }
            }
        }

        #endregion

        #region Properties

        public TextElement Source { get; private set; }

        public IEnumerable<IBlockElement> Blocks 
        {
            get
            {
                return Items.Cast<IBlockElement>();
            }
        }

        public bool BreakPageBefore { get; set; }

        #endregion

        #region IBlockElement members

        public virtual void AddChildElement(UIElement child)
        {
            this.Items.Add(child);
        }

        public abstract IBlockElement GetEmptyCopy();

        public IBlockElement GetPreviousBlock(IBlockElement block)
        {
            int index = Items.IndexOf(block as UIElement);
            if (index < 0)
                throw new ArgumentException("The provided block is not a child of this BlockContainer.");

            if (index == 0)
                return null;

            return Items[index - 1] as IBlockElement;
        }


        public virtual HarvestResult Harvest(HarvestingContext context, double remainingHeight, IBlockElement previousElement)
        {
            // If the block requires a page break, let's make sure we are the first in the document.
            if ((this.BreakPageBefore) && (PageColumnElement.IsDirectChild(this)))
            {
                if (previousElement != null)
                {
                    return HarvestResult.BreakPage;
                }
            }


            var adjustedMargin = GetAdjustedMargin(previousElement);
            remainingHeight -= adjustedMargin.Top;

            BlockContainerElement harvestedElement = null;
            IBlockElement previousChild = null;

            foreach (UIElement child in this.Items)
            {
                if (context.IsFullyHarvested(child))
                    continue;

                var childBlock = child as IBlockElement;
                var childResult = childBlock.Harvest(context, remainingHeight, previousChild);
                remainingHeight = childResult.RemainingHeight;

                // Let's see if child fit
                if (childResult.HarvestedElement == null)
                {
                    return new HarvestResult(harvestedElement, remainingHeight, childResult.AddColumnBreak, childResult.AddPageBreak);
                }
                else
                {
                    if (harvestedElement == null)
                    {
                        harvestedElement = this.GetEmptyCopy() as BlockContainerElement;
                        harvestedElement.Margin = adjustedMargin;
                    }

                    // Add child to harvested element
                    harvestedElement.AddChildElement(childResult.HarvestedElement);
                }

                previousChild = childBlock;
                if (this.Items.IndexOf(child) == Items.Count - 1)
                { 
                    // Last child. We are finished, if it is finished
                    if (context.IsFullyHarvested(child))
                        context.MarkAsFullyHarvested(this);
                }

                if (childResult.AddColumnBreak || childResult.AddPageBreak)
                    break;
            }
            return new HarvestResult(harvestedElement, remainingHeight, false, false);
        }

        protected virtual Thickness GetAdjustedMargin(IBlockElement previousElement)
        {
            double topMargin = 0;

            if (previousElement != null)
            {
                // Get the bottom margin of the previous element
                topMargin = previousElement.CalculateBottomMargin();

                // Apply our margin instead if it is bigger
                var thisTop = CalculateTopMargin();
                if (thisTop > topMargin)
                    topMargin = thisTop;
            }
            else
            {
                var block = Source as Block;

                if ((block != null) && (!double.IsNaN(block.Margin.Top)))
                    topMargin = block.Margin.Top;
            }

            return new Thickness(this.Margin.Left, topMargin, this.Margin.Right, 0);
        }


        public virtual double CalculateBottomMargin()
        {
            return 0;
        }

        public virtual double CalculateTopMargin()
        {
            return 0;
        }

        public double CalculateHeight()
        {
            if (!double.IsNaN(Height))
                return Height;

            return CalculateContentHeight();
        }

        public double CalculateContentHeight()
        {
            double result = 0;

            foreach (var child in Items)
            {
                var element = child as FrameworkElement;
                if (element != null)
                {
                    result += element.Margin.Top + element.ActualHeight;
                }
            }

            return result;
        }

        public virtual double CalculateMinHeight()
        {
            if (Items.Count > 0)
            {
                var block = Items[0] as IBlockElement;
                if (block != null)
                    return block.CalculateMinHeight();
            }

            return 0;
        }

        #endregion
    }
}
